package org.example.mq.dataCenter;

import org.example.mq.common.BinaryTool;
import org.example.mq.common.MqException;
import org.example.mq.mqserver.core.MSGQueue;
import org.example.mq.mqserver.core.Message;

import java.io.*;
import java.util.LinkedList;
import java.util.Scanner;

public class MessageFileManager {

    public void init() {

    }

    static public class Stat{
        public int totalCount;
        public int ValidCount;
    }
    /*
    * 消息文件，消息文件中有两个文件（一个存储消息，另一个存储相关信息）
    * */
    private String getQueueDir(String queueName){
        return "./data/"+queueName;
    }

//    存放数据（.bin,.dat）
    private String getQueueDataPath(String queueName){
        return getQueueDir(queueName)+"/queue_data.txt";
    }

//    存放状态信息
    private String getQueueStatPath(String queueName){
        return getQueueDir(queueName)+"/queue_stat.txt";
    }


    /*
    * 读出相关信息(文本文件，直接使用scanner进行读取)
    * */
    private Stat readStat(String queueName){
        Stat stat = new Stat();
        try(InputStream inputStream = new FileInputStream(getQueueStatPath(queueName))){
            Scanner scanner = new Scanner(inputStream);
            stat.totalCount = scanner.nextInt();
            stat.ValidCount = scanner.nextInt();
            return stat;
        } catch (IOException e) {
            e.printStackTrace();
        }
        return null;
    }

    /*
    * 写操作
    * */
    private void writeStat(String queueName,Stat stat){
        try(OutputStream outputStream = new FileOutputStream(getQueueStatPath(queueName))){
            PrintWriter printWriter = new PrintWriter(outputStream);
            printWriter.write(stat.totalCount+"\t"+ stat.ValidCount);
//            刷新缓存
            printWriter.flush();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /*
    * 文件初始化（真正的创建文件）
    * */
    public void createQueueFile(String queueName) throws IOException {
//        创建目录
        File baseFile = new File(getQueueDir(queueName));
        if(!baseFile.exists()){
            boolean ok = baseFile.mkdirs();
            if(!ok){
                throw new IOException("创建目录失败"+baseFile.getAbsoluteFile());
            }
        }
//        创建数据文件
        File queueDataFile = new File(getQueueDataPath(queueName));
        if(!queueDataFile.exists()){
            boolean ok = queueDataFile.createNewFile();
            if(!ok){
                throw new IOException("创建数据失败"+queueDataFile.getAbsoluteFile());
            }
        }
//        创建统计文件
        File queueStatFile = new File(getQueueStatPath(queueName));
        if(!queueStatFile.exists()){
            boolean ok = queueStatFile.createNewFile();
            if(!ok){
                throw new IOException("创建统计失败"+queueStatFile.getAbsoluteFile());
            }
        }
//        统计文件分配参数
        Stat stat = new Stat();
        stat.ValidCount = 0;
        stat.totalCount = 0;
        writeStat(queueName,stat);
    }


    /*
    * 删除目录（先删除文件，后删除目录）
    * */
    public void destroyFile(String queueName) throws IOException {
        File dataFile = new File(getQueueDataPath(queueName));
        boolean ok1 = dataFile.delete();
        File statFile = new File(getQueueStatPath(queueName));
        boolean ok2 = statFile.delete();
        File baseFile = new File(getQueueDir(queueName));
        boolean ok3 = baseFile.delete();
        if(!ok1 || !ok2 || !ok3){
            throw new IOException("目录删除失败"+baseFile.getAbsoluteFile());
        }
    }

    /*
    * 检查目录文件是否存在
    * */
    private boolean checkFilesExist(String queueFile){
        File dataFile = new File(getQueueDataPath(queueFile));
        if(!dataFile.exists()){
            return false;
        }
        File statFile = new File(getQueueStatPath(queueFile));
        if(!statFile.exists()){
            return false;
        }
        return true;
    }

    /*
    * 将消息写入文件(本质上是放入队列中)
    * */
    public void sendMessage(MSGQueue queue, Message message) throws MqException, IOException {
//       1. 放入的前提条件，文件必须存在（队列名和文件名一致）
        if (!checkFilesExist(queue.getName())) {
            throw new MqException("[MessageFileManager 文件不存在]");
        }
//     2. (序列化)  将消息转换为2进制
        byte[] bytes = BinaryTool.toBytes(message);
//     3.   规定写入消息在文件中的格式
        //消息的内容
        synchronized (queue) {
            File dataFile = new File(getQueueDataPath(queue.getName()));
            //System.out.println(dataFile.getAbsoluteFile());
            //C:\Users\Lenovo\Desktop\mq\mq\.\data\textQueueName1\queue_data.txt -- 实际的文件
            message.setOffsetBeg(dataFile.length() + 4);
            message.setOffsetEnd(dataFile.length() + 4 + bytes.length);
            //消息的头(内容的长度)
//        4.执行数据内容的写入
            try (OutputStream outputStream = new FileOutputStream(dataFile, true)) {
                try (DataOutputStream dataOutputStream = new DataOutputStream(outputStream)) {
                    //写入头（包含数据长度）不能直接写入数值，否则就是说占用1个字节
                    //这个方法本质上是将int中的4个字节，一个字节一个字节写入
                    dataOutputStream.writeInt(bytes.length);
                    dataOutputStream.write(bytes);
                }
            }
//       5.更新统计文件
            Stat stat = readStat(queue.getName());
            stat.totalCount += 1;
            stat.ValidCount += 1;
            writeStat(queue.getName(), stat);
        }
    }

    /*
    * 删除消息（逻辑删除）RandomAccessFile类存在光标，可以任意删除
    * */
    public void deleteMessage(MSGQueue queue,Message message) throws IOException, ClassNotFoundException {
        synchronized (queue) {
//        1.从文件中取出数据
            try (RandomAccessFile randomAccessFile = new RandomAccessFile(getQueueDataPath(queue.getName()), "rw")) {
                //174代码
                System.out.println(queue.getName());

                long fileLength = randomAccessFile.length();
                long offsetBeg = message.getOffsetBeg();
                long offsetEnd = message.getOffsetEnd();

                if (offsetBeg < 0 || offsetEnd > fileLength || offsetEnd <= offsetBeg) {
                    throw new IOException("Invalid message offsets: offsetBeg=" + offsetBeg + ", offsetEnd=" + offsetEnd + ", fileLength=" + fileLength);
                }

                int length = (int) (offsetEnd - offsetBeg);

                byte[] bytes = new byte[(int) (message.getOffsetEnd() - message.getOffsetBeg())];
                randomAccessFile.seek(message.getOffsetBeg());
                //从这个光标开始，一直读，直到填充满
                randomAccessFile.readFully(bytes);
//        2.将读出的数据进行反序列化
                Message message1 = (Message) BinaryTool.fromBytes(bytes);
//        3.将文件中的删除位修改
                message1.setIsValid((byte) 0x0);
//        4.将数据放回文件
                byte[] bytes1 = BinaryTool.toBytes(message1);
                randomAccessFile.seek(message.getOffsetBeg());
                randomAccessFile.write(bytes1);
            }
//        5.更新统计文件
            Stat stat = readStat(queue.getName());
            if (stat.ValidCount > 0) {
                stat.ValidCount -= 1;
            }
            writeStat(queue.getName(), stat);

        }
    }

    /*
    * 读取所有的消息（只是在启动的时候调用，不设计多线程问题）
    * 本质上也是一个消息一个消息读出来（循环）
    * */
    public LinkedList<Message> loadAllMessageFromQueue(String queue) throws IOException, MqException, ClassNotFoundException {
        LinkedList<Message> messages = new LinkedList<>();
        try(InputStream inputStream = new FileInputStream(getQueueDataPath(queue))){
            try(DataInputStream dataInputStream = new DataInputStream(inputStream)){
                long currentOffSet = 0;
                while(true){
//                    1.读取当前长度
                    //先读取四个字节（这四个字节表明长度是多少）
                    int messageSize = dataInputStream.readInt();
                    byte[] bytes = new byte[messageSize];
//                    2.根据长度读取消息数据
                    //返回实际读取了多少
                    int size = dataInputStream.read(bytes);
//                    3.进行判断格式是否有误，实际读取到的和四个字节中的是否一致
                    if(size!=messageSize){
                        throw new MqException("[MessageFileManager]文件数据格式有误");
                    }
//                    4.进行反序列化
                    //将读取到的数据进行反序列化
                    Message message = (Message) BinaryTool.fromBytes(bytes);
//                    5.判断是不是无效数据(如果不是有效数据，直接跳过)
                    if(message.getIsValid()!=0x1){
                        currentOffSet +=(4+messageSize);
                        continue;
                    }
//                    6.光标需要移动
                    message.setOffsetBeg(currentOffSet+4);
                    message.setOffsetEnd(currentOffSet+4+messageSize);
                    currentOffSet +=(4+messageSize);
                    messages.add(message);
                }
            }catch (EOFException e){
                //这里的异常并不是真的异常，还是借助异常来处理逻辑
//                DataInputStream类在读取完所有的数据之后，会抛出EOF异常，借用这个异常实现读取完成逻辑
                System.out.println("文件读取完成");
            }
        }
        return messages;
    }

    /*
    * 垃圾回收机制——检查是否需要垃圾回收(有效数据占总数据的一半)
    * */
    public boolean checkGC(String queueName){
        Stat stat = readStat(queueName);
        if(stat.totalCount>2000 && (double) stat.ValidCount /(double) stat.totalCount>0.5){
            return true;
        }
        return false;
    }

    /*
    * 垃圾回收机制——创建新的暂时存储数据文件
    * */
    public String getQueueDataNewPath(String queueName){
        return getQueueDir(queueName)+"/queue_data_new_file.txt";
    }

    /*
    * 垃圾回收机制——实现垃圾回收(真正的GC)
    * 1.先创建出新文件
    * 2.将有效数据读取到新文件中
    * 3.删除原来的旧文件
    * 4.将新文件进行改名
    * 5.更新统计
    * */
    public void gc(MSGQueue queue) throws MqException, IOException, ClassNotFoundException {
        synchronized (queue){
            long gcBegin = System.currentTimeMillis();
//            1.先创建出新文件
            File queueDataNewFile = new File(getQueueDataNewPath(queue.getName()));
            //判断是否存在
            if(queueDataNewFile.exists()){
                throw new MqException("[MessageFileManage] 垃圾回收中新文件已存在，不能继续创建,queueName = "+queue.getName());
            }
            boolean ok = queueDataNewFile.createNewFile();
            if(!ok){
                throw new MqException("[MessageFileManage] 垃圾回收中新文件创建失败,queueName = "+queue.getName());
            }
//            2.将有效数据读取到新文件中
            //取出有效文件
            LinkedList<Message> messages = loadAllMessageFromQueue(queue.getName());
            //执行写入操作
            try(OutputStream outputStream = new FileOutputStream(queueDataNewFile)){
                try(DataOutputStream dataOutputStream = new DataOutputStream(outputStream)){
                    for(Message message:messages){
                        byte[] bytes = BinaryTool.toBytes(message);
                        dataOutputStream.writeInt(bytes.length);
                        dataOutputStream.write(bytes);
                    }
                }
            }
//            3.删除原来的旧文件
            File queueDataOldFile = new File(getQueueDataPath(queue.getName()));
            ok = queueDataOldFile.delete();
            if(!ok){
                throw new MqException("[MessageFileManage] 垃圾回收中删除旧文件失败,queueName = "+queue.getName());
            }
//            4.将新文件进行改名
            ok = queueDataNewFile.renameTo(queueDataOldFile);
            if(!ok){
                throw new MqException("[MessageFileManage] 垃圾回收中新文件改名失败,queueName = "+queue.getName());
            }
//            5.更新统计
            Stat stat = readStat(queue.getName());
            stat.ValidCount = messages.size();
            stat.totalCount = messages.size();
            writeStat(queue.getName(),stat);

            long gcEnd = System.currentTimeMillis();
            System.out.println("[MessageFileManage] 垃圾回收结束,queueName = "+queue.getName()+",用时"+(gcEnd-gcBegin)+"ms");
        }
    }
}
